/******************************************************************/
/*****                                                        *****/
/*****     Project:           Adobe Color Picker Clone 1      *****/
/*****     Filename:          ColorBox.cs               *****/
/*****     Original Author:   Danny Blanchard                 *****/
/*****                        - scrabcakes@gmail.com          *****/
/*****     Updates:	                                          *****/
/*****      3/28/2005 - Initial Version : Danny Blanchard     *****/
/*****                                                        *****/
/******************************************************************/

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Windows.Forms;

namespace adobe_color_picker_clone_part_1
{
	/// <summary>
	/// Summary description for ColorBox.
	/// </summary>
	public class ColorBox : System.Windows.Forms.UserControl
	{
		#region Class Variables

		public enum eDrawStyle
		{
			Hue,
			Saturation,
			Brightness,
			Red,
			Green,
			Blue
		}

		private int		m_iMarker_X = 0;
		private int		m_iMarker_Y = 0;
		private bool	m_bDragging = false;

		private BufferedGraphicsContext bufferGC;
		private BufferedGraphics bufferG;

		//	These variables keep track of how to fill in the content inside the box;
		private eDrawStyle		m_eDrawStyle = eDrawStyle.Hue;
		private AdobeColors.HSL	m_hsl;
		private Color			m_rgb;

		/// <summary> 
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

		#endregion

		#region Constructors / Destructors

		public ColorBox()
		{
			bufferGC = BufferedGraphicsManager.Current;
			bufferG = bufferGC.Allocate(this.CreateGraphics(), new Rectangle(2, 2, this.Width - 9, this.Height - 9));
			
			// This call is required by the Windows.Forms Form Designer.
			InitializeComponent();

			//	Initialize Colors
			m_hsl = new AdobeColors.HSL();
			m_hsl.H = 1.0;
			m_hsl.S = 1.0;
			m_hsl.L = 1.0;
			m_rgb = AdobeColors.HSL_to_RGB(m_hsl);
			m_eDrawStyle = eDrawStyle.Hue;
		}


		/// <summary> 
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}


		#endregion

		#region Component Designer generated code
		/// <summary> 
		/// Required method for Designer support - do not modify 
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			this.SuspendLayout();
			// 
			// ColorBox
			// 
			this.Name = "ctrl2DColorBox";
			this.Size = new System.Drawing.Size(260, 260);
			this.Load += new System.EventHandler(this.ctrl2DColorBox_Load);
			this.Paint += new System.Windows.Forms.PaintEventHandler(this.ctrl2DColorBox_Paint);
			this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.ctrl2DColorBox_MouseDown);
			this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.ctrl2DColorBox_MouseMove);
			this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.ctrl2DColorBox_MouseUp);
			this.Resize += new System.EventHandler(this.ctrl2DColorBox_Resize);
			this.ResumeLayout(false);

		}
		#endregion

		#region Control Events

		private void ctrl2DColorBox_Load(object sender, System.EventArgs e)
		{
			Redraw_Control();
		}


		private void ctrl2DColorBox_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			if ( e.Button != MouseButtons.Left )	//	Only respond to left mouse button events
				return;

			m_bDragging = true;		//	Begin dragging which notifies MouseMove function that it needs to update the marker

			int x = e.X - 2, y = e.Y - 2;
			if ( x < 0 ) x = 0;
			if ( x > this.Width - 4 ) x = this.Width - 4;	//	Calculate marker position
			if ( y < 0 ) y = 0;
			if ( y > this.Height - 4 ) y = this.Height - 4;

			if ( x == m_iMarker_X && y == m_iMarker_Y )		//	If the marker hasn't moved, no need to redraw it.
				return;										//	or send a scroll notification

			DrawMarker(x, y, true);	//	Redraw the marker
			ResetHSLRGB();			//	Reset the color

			if ( Scroll != null )	//	Notify anyone who cares that the controls marker (selected color) has changed
				Scroll(this, e);
		}


		private void ctrl2DColorBox_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			if ( !m_bDragging )		//	Only respond when the mouse is dragging the marker.
				return;

			int x = e.X - 2, y = e.Y - 2;
			if ( x < 0 ) x = 0;
			if ( x > this.Width - 4 ) x = this.Width - 4;	//	Calculate marker position
			if ( y < 0 ) y = 0;
			if ( y > this.Height - 4 ) y = this.Height - 4;

			if ( x == m_iMarker_X && y == m_iMarker_Y )		//	If the marker hasn't moved, no need to redraw it.
				return;										//	or send a scroll notification

			DrawMarker(x, y, true);	//	Redraw the marker
			ResetHSLRGB();			//	Reset the color

			if ( Scroll != null )	//	Notify anyone who cares that the controls marker (selected color) has changed
				Scroll(this, e);
		}


		private void ctrl2DColorBox_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			if ( e.Button != MouseButtons.Left )	//	Only respond to left mouse button events
				return;

			if ( !m_bDragging )
				return;

			m_bDragging = false;

			int x = e.X - 2, y = e.Y - 2;
			if ( x < 0 ) x = 0;
			if ( x > this.Width - 4 ) x = this.Width - 4;	//	Calculate marker position
			if ( y < 0 ) y = 0;
			if ( y > this.Height - 4 ) y = this.Height - 4;

			if ( x == m_iMarker_X && y == m_iMarker_Y )		//	If the marker hasn't moved, no need to redraw it.
				return;										//	or send a scroll notification

			DrawMarker(x, y, true);	//	Redraw the marker
			ResetHSLRGB();			//	Reset the color

			if ( Scroll != null )	//	Notify anyone who cares that the controls marker (selected color) has changed
				Scroll(this, e);
		}


		private void ctrl2DColorBox_Resize(object sender, System.EventArgs e)
		{
			Redraw_Control();
		}


		private void ctrl2DColorBox_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
		{
			Redraw_Control();
			DrawBorder();
		}


		#endregion

		#region Events

		public new event EventHandler Scroll;

		#endregion

		#region Public Methods

		/// <summary>
		/// The drawstyle of the contol (Hue, Saturation, Brightness, Red, Green or Blue)
		/// </summary>
		public eDrawStyle DrawStyle
		{
			get
			{
				return m_eDrawStyle;
			}
			set
			{
				m_eDrawStyle = value;

				//	Redraw the control based on the new eDrawStyle
				Reset_Marker(true);
				Redraw_Control();
			}
		}


		/// <summary>
		/// The HSL color of the control, changing the HSL will automatically change the RGB color for the control.
		/// </summary>
		public AdobeColors.HSL HSL
		{
			get
			{
				return m_hsl;
			}
			set
			{
				m_hsl = value;
				m_rgb = AdobeColors.HSL_to_RGB(m_hsl);

				//	Redraw the control based on the new color.
				Reset_Marker(true);
				Redraw_Control();
			}
		}


		/// <summary>
		/// The RGB color of the control, changing the RGB will automatically change the HSL color for the control.
		/// </summary>
		public Color RGB
		{
			get
			{
				return m_rgb;
			}
			set
			{
				m_rgb = value;
				m_hsl = AdobeColors.RGB_to_HSL(m_rgb);

				//	Redraw the control based on the new color.
				Reset_Marker(true);
				Redraw_Control();
			}
		}


		#endregion

		#region Private Methods

		/// <summary>
		/// Redraws only the content over the marker
		/// </summary>
		private void ClearMarker()
		{
			Redraw_Control(false);
		}


		/// <summary>
		/// Draws the marker (circle) inside the box
		/// </summary>
		/// <param scriptName="x"></param>
		/// <param scriptName="y"></param>
		/// <param scriptName="Unconditional"></param>
		private void DrawMarker(int x, int y, bool Unconditional)			//	   *****
		{																	//	  *  |  *
			if (x < 0) x = 0;												//	 *   |   *
			if (x > this.Width - 4) x = this.Width - 4;						//	*    |    *
			if (y < 0) y = 0;												//	*    |    *
			if (y > this.Height - 4) y = this.Height - 4;					//	*----X----*
																			//	*    |    *
			if (m_iMarker_Y == y && m_iMarker_X == x && !Unconditional)		//	*    |    *
				return;														//	 *   |   *
																			//	  *  |  *
			ClearMarker();													//	   *****

			m_iMarker_X = x;
			m_iMarker_Y = y;

			Pen pen;
			AdobeColors.HSL _hsl = GetColor(x, y);	//	The selected color determines the color of the marker drawn over
			//	it (black or white)
			if (_hsl.L < (double)200 / 255)
				pen = new Pen(Color.White);									//	White marker if selected color is dark
			else if (_hsl.H < (double)26 / 360 || _hsl.H > (double)200 / 360)
				if (_hsl.S > (double)70 / 255)
					pen = new Pen(Color.White);
				else
					pen = new Pen(Color.Black);								//	Else use a black marker for lighter colors
			else
				pen = new Pen(Color.Black);

			bufferG.Graphics.DrawEllipse(pen, x - 3, y - 3, 10, 10);						//	Draw the marker : 11 x 11 circle
			bufferG.Render();

//			DrawBorder();		//	Force the border to be redrawn, just in case the marker has been drawn over it.
		}


		/// <summary>
		/// Draws the border around the control.
		/// </summary>
		private void DrawBorder()
		{
			Graphics g = this.CreateGraphics();

			Pen pencil;

			//	To make the control look like Adobe Photoshop'cmd the border around the control will be a gray line
			//	on the top and left side, a white line on the bottom and right side, and a black rectangle (line) 
			//	inside the gray/white rectangle

			pencil = new Pen(Color.LightGray);	//	The same gray color used by Photoshop
			g.DrawLine(pencil, this.Width - 2, 0, 0, 0);	//	Draw top line
			g.DrawLine(pencil, 0, 0, 0, this.Height - 2);	//	Draw left hand line

			pencil = new Pen(Color.LightGray);
			g.DrawLine(pencil, this.Width - 1, 0, this.Width - 1, this.Height - 1);	//	Draw right hand line
			g.DrawLine(pencil, this.Width - 1, this.Height - 1, 0, this.Height - 1);	//	Draw bottome line

			pencil = new Pen(Color.White);
			g.DrawRectangle(pencil, 1, 1, this.Width - 3, this.Height - 3);	//	Draw inner black rectangle
		}

		/// <summary>
		/// Draws the content of the control filling in all color values with the provided Hue value.
		/// </summary>
		private void Draw_Style_Hue()
		{
			AdobeColors.HSL hsl_start = new AdobeColors.HSL();
			AdobeColors.HSL hsl_end = new AdobeColors.HSL();
			hsl_start.H = m_hsl.H;
			hsl_end.H = m_hsl.H;
			hsl_start.S = 0.0;
			hsl_end.S = 1.0;

			for (int i = 0; i < this.Height - 4; i++)				//	For each horizontal line in the control:
			{
				hsl_start.L = 1.0 - (double)i / (this.Height - 4);	//	Calculate luminance at this line (Hue and Saturation are constant)
				hsl_end.L = hsl_start.L;

				LinearGradientBrush br = new LinearGradientBrush(new Rectangle(2, 2, this.Width - 4, 1), AdobeColors.HSL_to_RGB(hsl_start), AdobeColors.HSL_to_RGB(hsl_end), 0, false);

				bufferG.Graphics.FillRectangle(br, new Rectangle(2, i + 2, this.Width - 4, 1));
			}
		}


		/// <summary>
		/// Draws the content of the control filling in all color values with the provided Saturation value.
		/// </summary>
		private void Draw_Style_Saturation()
		{
			AdobeColors.HSL hsl_start = new AdobeColors.HSL();
			AdobeColors.HSL hsl_end = new AdobeColors.HSL();
			hsl_start.S = m_hsl.S;
			hsl_end.S = m_hsl.S;
			hsl_start.L = 1.0;
			hsl_end.L = 0.0;

			for ( int i = 0; i < this.Width - 4; i++ )		//	For each vertical line in the control:
			{
				hsl_start.H = (double)i/(this.Width - 4);	//	Calculate Hue at this line (Saturation and Luminance are constant)
				hsl_end.H = hsl_start.H;
				
				LinearGradientBrush br = new LinearGradientBrush(new Rectangle(2,2, 1, this.Height - 4), AdobeColors.HSL_to_RGB(hsl_start), AdobeColors.HSL_to_RGB(hsl_end), 90, false);
				bufferG.Graphics.FillRectangle(br, new Rectangle(i + 2, 2, 1, this.Height - 4)); 
			}
		}


		/// <summary>
		/// Draws the content of the control filling in all color values with the provided Luminance or Brightness value.
		/// </summary>
		private void Draw_Style_Luminance()
		{
			AdobeColors.HSL hsl_start = new AdobeColors.HSL();
			AdobeColors.HSL hsl_end = new AdobeColors.HSL();
			hsl_start.L = m_hsl.L;
			hsl_end.L = m_hsl.L;
			hsl_start.S = 1.0;
			hsl_end.S = 0.0;

			for ( int i = 0; i < this.Width - 4; i++ )		//	For each vertical line in the control:
			{
				hsl_start.H = (double)i/(this.Width - 4);	//	Calculate Hue at this line (Saturation and Luminance are constant)
				hsl_end.H = hsl_start.H;
				
				LinearGradientBrush br = new LinearGradientBrush(new Rectangle(2,2, 1, this.Height - 4), AdobeColors.HSL_to_RGB(hsl_start), AdobeColors.HSL_to_RGB(hsl_end), 90, false);
				bufferG.Graphics.FillRectangle(br, new Rectangle(i + 2, 2, 1, this.Height - 4)); 
			}
		}


		/// <summary>
		/// Draws the content of the control filling in all color values with the provided Red value.
		/// </summary>
		private void Draw_Style_Red()
		{

			int red = m_rgb.R;;

			for ( int i = 0; i < this.Height - 4; i++ )				//	For each horizontal line in the control:
			{
				//	Calculate Green at this line (Red and Blue are constant)
				int green = (int)Math.Round(255 - (255 * (double)i / (this.Height - 4)));

				LinearGradientBrush br = new LinearGradientBrush(new Rectangle(2,2, this.Width - 4, 1), Color.FromArgb(red, green, 0), Color.FromArgb(red, green, 255), 0, false);
				bufferG.Graphics.FillRectangle(br, new Rectangle(2, i + 2, this.Width - 4, 1)); 
			}
		}


		/// <summary>
		/// Draws the content of the control filling in all color values with the provided Green value.
		/// </summary>
		private void Draw_Style_Green()
		{

			int green = m_rgb.G;;

			for ( int i = 0; i < this.Height - 4; i++ )	//	For each horizontal line in the control:
			{
				//	Calculate Red at this line (Green and Blue are constant)
				int red = (int)Math.Round(255 - (255 * (double)i / (this.Height - 4)));

				LinearGradientBrush br = new LinearGradientBrush(new Rectangle(2,2, this.Width - 4, 1), Color.FromArgb(red, green, 0), Color.FromArgb(red, green, 255), 0, false);
				bufferG.Graphics.FillRectangle(br, new Rectangle(2, i + 2, this.Width - 4, 1)); 
			}
		}


		/// <summary>
		/// Draws the content of the control filling in all color values with the provided Blue value.
		/// </summary>
		private void Draw_Style_Blue()
		{

			int blue = m_rgb.B;;

			for ( int i = 0; i < this.Height - 4; i++ )	//	For each horizontal line in the control:
			{
				//	Calculate Green at this line (Red and Blue are constant)
				int green = (int)Math.Round(255 - (255 * (double)i / (this.Height - 4)));

				LinearGradientBrush br = new LinearGradientBrush(new Rectangle(2,2, this.Width - 4, 1), Color.FromArgb(0, green, blue), Color.FromArgb(255, green, blue), 0, false);
				bufferG.Graphics.FillRectangle(br, new Rectangle(2, i + 2, this.Width - 4, 1)); 
			}
		}


		/// <summary>
		/// Calls all the functions neccessary to redraw the entire control.
		/// </summary>
		private void Redraw_Control(bool isDrawMaker = true)
		{
			switch (m_eDrawStyle)
			{
				case eDrawStyle.Hue :
					Draw_Style_Hue();
					break;
				case eDrawStyle.Saturation :
					Draw_Style_Saturation();
					break;
				case eDrawStyle.Brightness :
					Draw_Style_Luminance();
					break;
				case eDrawStyle.Red :
					Draw_Style_Red();
					break;
				case eDrawStyle.Green :
					Draw_Style_Green();
					break;
				case eDrawStyle.Blue :
					Draw_Style_Blue();
					break;
			}

			if (isDrawMaker)
			{
				DrawMarker(m_iMarker_X, m_iMarker_Y, true);
				bufferG.Render();
			}
		}


		/// <summary>
		/// Resets the marker position of the slider to match the controls color.  Gives the option of redrawing the slider.
		/// </summary>
		/// <param scriptName="Redraw">Set to true if you want the function to redraw the slider after determining the best position</param>
		private void Reset_Marker(bool Redraw)
		{
			switch (m_eDrawStyle)
			{
				case eDrawStyle.Hue :
					m_iMarker_X = (int)Math.Round((this.Width - 4) * m_hsl.S);
					m_iMarker_Y = (int)Math.Round((this.Height - 4) * (1.0 - m_hsl.L));
					break;
				case eDrawStyle.Saturation :
					m_iMarker_X = (int)Math.Round((this.Width - 4) * m_hsl.H);
					m_iMarker_Y = (int)Math.Round((this.Height - 4) * (1.0 - m_hsl.L));
					break;
				case eDrawStyle.Brightness :
					m_iMarker_X = (int)Math.Round((this.Width - 4) * m_hsl.H);
					m_iMarker_Y = (int)Math.Round((this.Height - 4) * (1.0 - m_hsl.S));
					break;
				case eDrawStyle.Red :
					m_iMarker_X = (int)Math.Round((this.Width - 4) * (double)m_rgb.B / 255);
					m_iMarker_Y = (int)Math.Round((this.Height - 4) * (1.0 - (double)m_rgb.G / 255));
					break;
				case eDrawStyle.Green :
					m_iMarker_X = (int)Math.Round((this.Width - 4) * (double)m_rgb.B / 255);
					m_iMarker_Y = (int)Math.Round((this.Height - 4) * (1.0 - (double)m_rgb.R / 255));
					break;
				case eDrawStyle.Blue :
					m_iMarker_X = (int)Math.Round((this.Width - 4) * (double)m_rgb.R / 255);
					m_iMarker_Y = (int)Math.Round((this.Height - 4) * (1.0 - (double)m_rgb.G / 255));
					break;
			}

			if ( Redraw )
				DrawMarker(m_iMarker_X, m_iMarker_Y, true);
		}


		/// <summary>
		/// Resets the controls color (both HSL and RGB variables) based on the current marker position
		/// </summary>
		private void ResetHSLRGB()
		{
			int red, green, blue;

			switch (m_eDrawStyle)
			{
				case eDrawStyle.Hue :
					m_hsl.S = (double)m_iMarker_X/(this.Width - 4);
					m_hsl.L = 1.0 - (double)m_iMarker_Y/(this.Height - 4);
					m_rgb = AdobeColors.HSL_to_RGB(m_hsl);
					break;
				case eDrawStyle.Saturation :
					m_hsl.H = (double)m_iMarker_X/(this.Width - 4);
					m_hsl.L = 1.0 - (double)m_iMarker_Y/(this.Height - 4);
					m_rgb = AdobeColors.HSL_to_RGB(m_hsl);
					break;
				case eDrawStyle.Brightness :
					m_hsl.H = (double)m_iMarker_X/(this.Width - 4);
					m_hsl.S = 1.0 - (double)m_iMarker_Y/(this.Height - 4);
					m_rgb = AdobeColors.HSL_to_RGB(m_hsl);
					break;
				case eDrawStyle.Red :
					blue = (int)Math.Round(255 * (double)m_iMarker_X / (this.Width - 4));
					green = (int)Math.Round(255 * (1.0 - (double)m_iMarker_Y / (this.Height - 4)));
					m_rgb = Color.FromArgb(m_rgb.R, green, blue);
					m_hsl = AdobeColors.RGB_to_HSL(m_rgb);
					break;
				case eDrawStyle.Green :
					blue = (int)Math.Round(255 * (double)m_iMarker_X / (this.Width - 4));
					red = (int)Math.Round(255 * (1.0 - (double)m_iMarker_Y / (this.Height - 4)));
					m_rgb = Color.FromArgb(red, m_rgb.G, blue);
					m_hsl = AdobeColors.RGB_to_HSL(m_rgb);
					break;
				case eDrawStyle.Blue :
					red = (int)Math.Round(255 * (double)m_iMarker_X / (this.Width - 4));
					green = (int)Math.Round(255 * (1.0 - (double)m_iMarker_Y / (this.Height - 4)));
					m_rgb = Color.FromArgb(red, green, m_rgb.B);
					m_hsl = AdobeColors.RGB_to_HSL(m_rgb);
					break;
			}
		}

		/// <summary>
		/// Returns the graphed color at the x,y position on the control
		/// </summary>
		/// <param scriptName="x"></param>
		/// <param scriptName="y"></param>
		/// <returns></returns>
		private AdobeColors.HSL GetColor(int x, int y)
		{

			AdobeColors.HSL _hsl = new AdobeColors.HSL();

			switch (m_eDrawStyle)
			{
				case eDrawStyle.Hue :
					_hsl.H = m_hsl.H;
					_hsl.S = (double)x/(this.Width - 4);
					_hsl.L = 1.0 - (double)y/(this.Height - 4);
					break;
				case eDrawStyle.Saturation :
					_hsl.S = m_hsl.S;
					_hsl.H = (double)x/(this.Width - 4);
					_hsl.L = 1.0 - (double)y/(this.Height - 4);
					break;
				case eDrawStyle.Brightness :
					_hsl.L = m_hsl.L;
					_hsl.H = (double)x/(this.Width - 4);
					_hsl.S = 1.0 - (double)y/(this.Height - 4);
					break;
				case eDrawStyle.Red :
					_hsl = AdobeColors.RGB_to_HSL(Color.FromArgb(m_rgb.R, (int)Math.Round(255 * (1.0 - (double)y / (this.Height - 4))), (int)Math.Round(255 * (double)x / (this.Width - 4))));
					break;
				case eDrawStyle.Green :
					_hsl = AdobeColors.RGB_to_HSL(Color.FromArgb((int)Math.Round(255 * (1.0 - (double)y / (this.Height - 4))), m_rgb.G, (int)Math.Round(255 * (double)x / (this.Width - 4))));
					break;
				case eDrawStyle.Blue :
					_hsl = AdobeColors.RGB_to_HSL(Color.FromArgb((int)Math.Round(255 * (double)x / (this.Width - 4)), (int)Math.Round(255 * (1.0 - (double)y / (this.Height - 4))), m_rgb.B));
					break;
			}

			return _hsl;
		}


		#endregion
	}
}
